package file

import (
	"io"
	"os"
	"path/filepath"
	"strings"

	"gitee.com/tomatomeatman/golang-repository/bricks/model"
	Log "github.com/cihub/seelog"
)

type FileUtil struct{}

// 读取文件
func (fu FileUtil) ReadFromFile(path string) *model.MsgEmity {
	if !fu.IsExist(path) {
		return model.MsgEmity{}.Err(1001, "文件不存在")
	}

	data, err := os.ReadFile(path)
	if err != nil {
		return model.MsgEmity{}.Err(1002, "读取失败")
	}

	return model.MsgEmity{}.Success(string(data), "读取成功")
}

// 读取文件,返回[]byte
func (fu FileUtil) Read(path string) *model.MsgEmity {
	if !fu.IsExist(path) {
		return model.MsgEmity{}.Err(1001, "文件不存在")
	}

	data, err := os.ReadFile(path)
	if err != nil {
		return model.MsgEmity{}.Err(1002, "读取失败")
	}

	return model.MsgEmity{}.Success(data, path)
}

// 调用os.MkdirAll递归创建文件夹
func (fu FileUtil) CreateFile(filePath string) (bool, error) {
	if !fu.IsExist(filePath) {
		err := os.MkdirAll(filePath, os.ModePerm)
		return false, err
	}

	return true, nil
}

// 判断所给路径文件/文件夹是否存在(返回true是存在)
func (fu FileUtil) IsExist(path string) bool {
	_, err := os.Stat(path) //os.Stat获取文件信息
	if err != nil {
		if os.IsExist(err) {
			return true
		}
		return false
	}

	return true
}

// 保存文件内容
func (fu FileUtil) Save(text string, path string) (bool, error) {
	// ok, err := f.CreateFile(path)
	// if err != nil {
	// 	return false, err
	// }

	// if !ok {
	// 	return false, err
	// }

	iEd := strings.LastIndex(path, "/")
	dir := path[:iEd]
	if "." != dir {
		os.MkdirAll(dir, os.ModePerm)
	}

	//使用 os.WriteFile写文件，在写入文件之前，不需要判断文件是否存在，如果文件不存在，会自动创建文件，如果文件存在，则会覆盖原来的内容
	err := os.WriteFile(path, []byte(text), 0666) // 保存到文件
	if err != nil {
		return false, err
	}

	return true, nil
}

// 删除文件
func (fu FileUtil) Del(path string) (bool, error) {
	err := os.Remove(path)
	if err == nil { // 删除成功
		return true, err
	}

	// 删除失败
	ok := fu.IsExist(path)
	if ok {
		return true, nil
	}

	Log.Error("删除文件失败:", err)

	return false, err
}

// 获取指定目录下的所有文件和目录
func (fu FileUtil) GetFilesAndDirs(dirPth string) (files []string, dirs []string, err error) {
	dir, err := os.ReadDir(dirPth)
	if err != nil {
		return nil, nil, err
	}

	PthSep := string(os.PathSeparator)
	//suffix = strings.ToUpper(suffix) //忽略后缀匹配的大小写

	for _, fi := range dir {
		if fi.IsDir() { // 目录, 递归遍历
			dirs = append(dirs, dirPth+PthSep+fi.Name())
			fu.GetFilesAndDirs(dirPth + PthSep + fi.Name())
		} else {
			// 过滤指定格式
			//if strings.HasSuffix(fi.Name(), ".go") {
			files = append(files, dirPth+PthSep+fi.Name())
			//}
		}
	}

	return files, dirs, nil
}

// 获取指定目录下的所有文件,包含子目录下的文件
func (fu FileUtil) GetAllFiles(dirPth string) (files []string, err error) {
	var dirs []string
	dir, err := os.ReadDir(dirPth)
	if err != nil {
		return nil, err
	}

	PthSep := string(os.PathSeparator)
	//suffix = strings.ToUpper(suffix) //忽略后缀匹配的大小写

	for _, fi := range dir {
		if fi.IsDir() { // 目录, 递归遍历
			dirs = append(dirs, dirPth+PthSep+fi.Name())
			fu.GetAllFiles(dirPth + PthSep + fi.Name())
		} else {
			// 过滤指定格式
			//if strings.HasSuffix(fi.Name(), ".go") {
			files = append(files, dirPth+PthSep+fi.Name())
			//}
		}
	}

	// 读取子目录下文件
	for _, table := range dirs {
		temp, _ := fu.GetAllFiles(table)
		for _, temp1 := range temp {
			files = append(files, temp1)
		}
	}

	return files, nil
}

/**
 * 文件信息信息
 * @author HuangXinBian
 */
type FileInfo struct {
	GsName    string      `json:"sName"`    //名称
	GsPath    string      `json:"sPath"`    //路径
	GiDir     int         `json:"iDir"`     //是否目录
	GiSize    int64       `json:"iSize"`    //大小
	GdTime    string      `json:"dTime"`    //修改时间
	Gchildren []*FileInfo `json:"children"` //子文件
}

/**
 * 取文件夹信息树
 * rootName 根节点
 * delRoot 路径中隐藏根路径
 */
func (fu FileUtil) GetFilesTree(rootName string, delRoot bool) *model.MsgEmity {
	rootName = strings.TrimSpace(rootName)
	root, err := os.Stat(rootName)
	if err != nil {
		return model.MsgEmity{}.Err(1001, "没有数据!")
	}

	sPath := fu.changePath(rootName)
	if delRoot {
		sPath = strings.Replace(sPath, rootName, "", 1) //0表示不替换任何符合数据
	}

	fileInfo := FileInfo{
		GsName: root.Name(),
		GsPath: sPath,
		GiSize: root.Size(),
		GdTime: root.ModTime().Format("2006-01-02 15:04:05"),
		GiDir:  1,
	}

	if delRoot {
		fu.buildTree(&fileInfo, rootName, rootName)
	} else {
		fu.buildTree(&fileInfo, rootName, "")
	}

	fu.printTree(&fileInfo)

	return model.MsgEmity{}.Success(fileInfo, "构造成功!")
}

// 递归遍历文件夹及其子文件夹，构建节点树
func (fu FileUtil) buildTree(node *FileInfo, vPath, replaceRoot string) {
	files, err := os.ReadDir(vPath)
	if err != nil {
		Log.Error("构建节点树异常:", err)
		return
	}

	for _, file := range files {
		if ".keep" == file.Name() {
			continue
		}

		info, _ := file.Info()
		fileInfo := FileInfo{}
		fileInfo.GsName = file.Name()
		fileInfo.GiSize = info.Size()
		fileInfo.GdTime = info.ModTime().Format("2006-01-02 15:04:05")

		if !file.IsDir() {
			sPath := fu.changePath(vPath + "/" + fileInfo.GsName)
			if replaceRoot != "" {
				sPath = strings.Replace(sPath, replaceRoot, "", 1) //0表示不替换任何符合数据
			}

			fileInfo.GsPath = sPath
			fileInfo.GiSize = info.Size()
			fileInfo.GiDir = 0
			node.Gchildren = append(node.Gchildren, &fileInfo)
			continue
		}

		childPath := filepath.Join(vPath, file.Name())
		sPath := fu.changePath(childPath)
		if replaceRoot != "" {
			sPath = strings.Replace(sPath, replaceRoot, "", 1) //0表示不替换任何符合数据
		}

		fileInfo.GsPath = sPath
		fileInfo.GiDir = 1

		node.Gchildren = append(node.Gchildren, &fileInfo)
		fu.buildTree(&fileInfo, childPath, replaceRoot)
	}
}

// 递归节点树
func (fu FileUtil) printTree(node *FileInfo) {
	for _, child := range node.Gchildren {
		fu.printTree(child)
	}
}

// 格式化路径
func (fu FileUtil) changePath(vPath string) string {
	vPath = strings.Replace(vPath, "\\", "/", -1)

	if !strings.HasPrefix(vPath, "./") {
		vPath = "./" + vPath
	}

	return vPath
}

// 判断所给路径是否为文件夹
func (fu FileUtil) IsDir(path string) bool {
	s, err := os.Stat(path)
	if err != nil {
		return false
	}

	return s.IsDir()
}

// 判断所给路径是否为文件
func (fu FileUtil) IsFile(path string) bool {
	s, err := os.Stat(path)
	if err != nil {
		return false
	}

	return !s.IsDir()
}

// 删除文件(文件夹)
func (fu FileUtil) DelFiles(dir string, delDir bool) error {
	d, err := os.Open(dir)
	if err != nil {
		return err
	}
	defer d.Close()

	names, err := d.Readdirnames(-1)
	if err != nil {
		return err
	}

	for _, name := range names {
		err = os.RemoveAll(dir + "/" + name)
		if err != nil {
			Log.Error("删除失败:", err)
			return err
		}
	}

	if !delDir {
		return nil
	}

	d.Close()

	err = os.Remove(dir)
	if err != nil {
		Log.Error("删除失败:", err)
		return err
	}

	return nil
}

/**
 * 创建文件所在文件夹
 * 注意:必须是文件,否则只能创建出上级路径
 * @param filePath 文件路径
 * @return bool:是否成功(如果路径已经存在则不会创建,但返回true); error:错误信息
 */
func (fu FileUtil) CreateFileDir(filePath string) (bool, error) {
	path := filepath.Dir(filePath) //获取上级目录
	_, err := os.Stat(path)        //os.Stat获取文件信息
	if err == nil {                //获取成功,说明文件存在
		return true, nil
	}

	if os.IsExist(err) { //再次判断路径是否存在,也可以使用os.IsNotExist(err)来判断是否不存在
		return true, nil
	}

	err = os.MkdirAll(path, os.ModePerm)
	if err != nil {
		return false, err
	}

	return true, nil
}

/**
 * 判断文件夹是否存在
 * @param filePath 文件路径
 * @return bool, error:是否存在(存在返回true); error:错误信息
 */
func (fu FileUtil) Exists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}

	if os.IsNotExist(err) {
		return false, nil
	}

	return false, err
}

/**
 * 获取当前执行程序目录
 * @return string 路径
 */
func (fu FileUtil) CurrentDir() string {
	//返回绝对路径  filepath.Dir(os.Args[0])去除最后一个元素的路径
	dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
	if err != nil {
		Log.Error("获取程序目录异常:", err)
		return "."
	}

	//将\替换成/
	return strings.Replace(dir, "\\", "/", -1)
}

// 复制文件夹
func (FileUtil) CopyDir(sourceDirPath, targetDirPath string) (bool, error) {
	// 检查源文件夹是否存在
	if _, err := os.Stat(sourceDirPath); os.IsNotExist(err) {
		return false, err
	}

	if strings.HasPrefix(sourceDirPath, "./") { //以"./"开头必须处理,否则会在"srcPath[len(sourceDirPath):]"获取错误
		appRoot, err := os.Getwd()
		if err != nil {
			return false, err
		}

		sourceDirPath = appRoot + sourceDirPath[1:] //去除"./",加入应用根目录
	}

	// 遍历源文件夹中的所有文件和子目录
	err := filepath.Walk(sourceDirPath, func(srcPath string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		// 计算目标路径
		temp := srcPath[len(sourceDirPath):]
		targetPath := filepath.Join(targetDirPath, temp)

		// 如果是目录，则创建目标目录
		if info.IsDir() {
			return os.MkdirAll(targetPath, info.Mode())
		}

		// 如果是文件，则复制文件
		srcFile, err := os.Open(srcPath)
		if err != nil {
			return err
		}
		defer srcFile.Close()

		targetFile, err := os.Create(targetPath)
		if err != nil {
			return err
		}
		defer targetFile.Close()

		_, err = io.Copy(targetFile, srcFile)
		if err != nil {
			return err
		}

		return os.Chmod(targetPath, info.Mode())
	})

	if err != nil {
		return false, err
	}

	return true, nil
}
